Университет Города Переславля
Программирование для Internet
 
"Будь карта у Колумба - не он открыл бы Америку."
(А.И. Герцен)

Лекция 5. JavaScript. Объектная модель

Речь сегодня пойдет о создании собственных объектов, о наследовании и полиморфизме. Это самый трудный, но и самый ключевой материал всего цикла о JavaScript.

Вам повезло, что Вы знакомитесь с этим материалом на примере именно JavaScript. В этом языке воможности наследования такие гибкие и мощные, что наследование в C++ и в Java покажется Вам лишь бледной тенью того, что Вы увидите здесь.

Таким образом, тот из вас, кто поймет и усвоит материал сегодняшней лекции, может считать себя уже почти проффессионалами и, кроме того, может считать, что все самое главное, что есть в языках C++ и Java, уже благополучно усвоено.

Создание простых объектов

Напомним, что объектом в JavaScript (как и в программировании, вообще) называется каким-либо образом определенный набор данных (свойств) и процедур их обработки (методов), которые рассматриваются как единое целое.

Конструкторы

Для того, чтобы создать объект, достаточно определить функцию - конструктор. Конструктор - это функция, имя которой пишется в операторе new. Ее роль - создать и проинициализировать экземпляр объекта. Т.е. определить какие методы и свойства будут присутствовать в объекте и назначить начальные значения свойтвам.

Для того, чтобы связать с объектом некоторые свойтсва и методы, конструктор использует ключевое слово this. В общем случае, это ключевое слово следует понимать как "данный экземпляр объекта".

Напишем для примера конструктор объекта Person. Параметром конструктора будет имя человека. Объект Person будет иметь единственное свойство name.

function Person(nm) {
   this.name = nm;
}

Обратите внимание, что имя функции Person мы начинаем с заглавной буквы, как это принято для имен объектов.

Этот объект, в достаточной мере, бесполезен. Мы не можем с ним ничего сделать, кроме как создать экземпляр(ы).

var ivanov = new Person("Иванов И.И.");
var petrov = new Person("Петров П.П.");

Остановимся подробнее на роли this. Как уже было сказано, this всегда означает "данный экземпляр объекта". Значит, когда конструктор Person() будет вызван из первой строки (для создания экземпляра ivanov), свойство name этого экземпляра получит значение "Иванов И.И.". Когда же конструктор будет вызван во второй строке, в контексте создаваемого экземпляра petrov, значение "Петров П.П." будет присвоено свойтсву name экземпляра petrov.

Таким образом, мы имеем два экземпляра объекта Person(), но свойство name у каждого из них имеет свое уникальное значение. Вспомните аналогию с двумя, настроенными на разные станции, приемниками.

this можно использовать не только в конструкторе, но и в любом методе, для указания, что речь идет о том экземпляре в контексте которого данный метод был вызван.

toString()

Правда, осталось непонятным, что нам теперь делать с созданными экземплярами. Попробуем напечатать, что-ли:

document.write("ivanov="+ivanov);
document.write("<BR>petrov="+petrov);

Получается нечто невразумительное:

Действительно, ну откуда системе знать как печатать экземпляр нашего объекта? Можно добиться лучшего результата, если печатать не сам объект, а его свойство name.

document.write("ivanov="+ivanov.name);
document.write("<BR>petrov="+petrov.name);

Это уже более похоже на то, что мы хотели получить:

Однако, всякий раз писать имя свойства, довольно скучно. Кроме того, мы можем захотеть, чтобы печаталось не одно свойство, а несколько сразу, или печаталась ячейка таблицы или просто все печаталось нестандартным шрифтом.

Решением проблемы является метод toString(). Этот метод всегда вызывается системой, когда она "хочет" представить объект в виде строки. Дополним наш объект этим методом.

function Person(nm) {
   this.name = nm;
   this.toString = _personToString;
}

function _personToString() {
   return "<FONT SIZE=+3><I>"+this.name+"</I></FONT>";
}

Обратите внимание, как мы добавляли метод. Чтобы связать некий метод с объектом, мы должны записать ключевое слово this, через точку, имя метода и, всему этому, присвоить имя реальной функции, реализующей метод (без скобок!). Имя реальной функции может быть любым. В принципе, оно может совпадать с именем метода, но не стоит этим злоупотреблять. Это может запутать программу. Например, я для себя решил, что все имена функций, реализующих методы, буду начинать с символа "_", чтобы не путать их с "обычными" функциями и всегда этому правилу следую. Вы можете придумать нечто свое, лишь бы Вам было удобно работать.

Еще раз напомним, о роли this. Функция _personToString() не может использовать просто name, потому, что name не имеет смысла без указания экземпляра. Ведь значение этого свойства различно в различных экземплярах. В то время, как this.name явно указывает на то, что имеется в виду значения свойства name того экземпляра, в контексте которого был вызван метод (функция _personToString()).

Попробуем теперь напечатать:

document.write("ivanov="+ivanov);
document.write("<BR>petrov="+petrov);

Все должно получиться нормально, без неожиданностей:

Реальный пример

Теперь, когда мы уже немного разобрались с основами, попробуем создать более полезный объект. К тому же, он нам пригодится в следующем разделе.

Итак, создадим объект Man со следующими методами:

Man() Создает экземпляр объекта. Получает два параметра. Первый - полное имя человека в виде "Фамилия Имя Отчество", второй - дата рождения.
getName() Если параметр опущен или указано false, возвращает имя в виде Фамилия И.О. Если указан параметр true - возвращает полное имя.
getAge() Возвращает возраст в полных годах.
isMan() Возвращает true, если этот человек - мужчина.
toString() Возвращает данный объект в строковом виде, пригодном для печати.

Текст программы может выглядеть, например, так:

function Man(nm,bd) {
   this.getName = _getName;
   this.getAge = _getAge;
   this.isMan = _isMan;
   this.toString = _manToString;
   this.birthDay = bd;
   this.nameArray = nm.split(" ");
}

function _getName(full) {
   with (this) {
      return (full) ? nameArray.join(" ") :
         nameArray[0]+" "+
         nameArray[1].charAt(0)+"."+
         nameArray[2].charAt(0)+".";
   }        
}

function _manToString() {
   return this.getName()+
      " пол:"+((this.isMan())? "М":"Ж")+
      " возраст:"+this.getAge();
}

function _getAge() {
   var year = (new Date()).getYear();
   if (year < 1900) year += 1900;
   var byear = this.birthDay.getYear();
   if (byear < 1900) byear += 1900;
   return year - byear; 
}

function _isMan() {
   var fathName = this.nameArray[2];
   return fathName.charAt(fathName.length-1) != "а";
}

Обратите внимание, что имя функции Man мы начинаем с заглавной буквы, как это принято для имен объектов.

Создадим массив экземпляров этого объекта и напечатаем его, чтобы убедиться, что все работает правильно.

var people = new Array();
people[people.length] = new Man("Иванов Иван Иванович",new Date(78,1,15));
people[people.length] = new Man("Петров Петр Петрович",new Date(79,2,13));
people[people.length] = new Man("\Сидоров \Сидор \Сидорович",new Date(80,6,23));
people[people.length] = new Man("Иванова Мари\я Ивановна",new Date(81,3,8));
people[people.length] = new Man("Петрова Александра Николаевна",new Date(78,9,1));
people[people.length] = new Man("\Сидорова Надежда Юрьевна",new Date(79,2,14));

for (var i=0; i < people.length; i++) {
   document.write(people[i]+"<BR>");
}
Напечатано будет:

Вроде, все в порядке? Будем считать, что теперь мы умеем создавать простые объекты!

Цикл for in

Когда мы говорили о циклах в JavaScript, мы не рассматривали еще один важный цикл - for in. Это цикл по свойствам экземпляра объекта. Рассмотрим пример:

document.write("Свойства объекта ivanov<OL>");
for (var i in ivanov) {
   document.write("<LI>"+i+"="+ivanov[i]+"</LI>");
}
document.write("</OL>");

Вот, что будет напечатано:

Переменная-индекс, сначала принимает занчение равное имени первого свойства (обыкновенная символьная строка), выполняется тело цикла. Затем, индекс становится равным имени второго свойства и снова выполняется тело цикла. И так далее. Таким образом перемнная-индекс последовательно принимает значения названий всех свойств экземпляра ivanov и для каждого выполняется тело цикла.

Далее, при печати, мы записываем имя экземпляра и, в квадратных скобках, симовльную переменную, содержащую имя свойства. Это альтернативный путь для доступа к свойствам. Всегда, вместо ivanov.name, можно записать ivanov["name"]. Результат будет такой же.

Попробуем посмотреть, какие свойства есть у нашего любимого объекта document. Их у него очень много. Программа будет выглядеть также:

document.write("Свойства объекта document<OL>");
for (var i in document) {
   document.write("<LI>"+i+"="+document[i]+"</LI>");
}
document.write("</OL>");

Вот, что будет напечатано:

Цикл for in очень часто применяется для отладки программ и не только для этого. Например, в разных браузерах объект document имеет различный набор свойств. Иногда такой цикл - единственное средство узнать что-либо об объекте.

Оператор with

В JavaScript есть еще один полезный оператор, который мы до сих пор не рассматривали. Это оператор with. По смыслу, он похож на своего тезку из языка Pascal и служит для сокращения записи. Если, записать ключевое слово with, а за ним, в скобках, имя экземпляра, то, в следующем операторе, можно использовать свойства этого экземпляра не ссылаясь на него. Например:

document.write(ivanov.name);

// тоже самое
with (ivanov) document.write(name);

Как и везде, вместо одного оператора можно использовать несколько, но нужно заключить их в фигурные скобки.

Назначение методов и свойств существующим объектам

Прекрасно, теперь мы умеем содать объект и связать с ним свойства и методы. Возникает вопрос, а можно ли привязать новое свойство или метод к уже существующему объекту?

Если бы мы имели дело с языками C++ или Java, то ответ был бы "нет". Однако, JavaScript чрезвычайно гибкий язык и в нем такие (и не такие еще!) фокусы вполне возможны!

Допустим, что у нас есть функция, которая, если бы ее удалось встроить в системный объект Date, выдавала бы дату по русски. Например, такая:

function _dateRussian() {
   var days = ["Воскресенье","Понедельник","Вторник",
               "Среда","Четверг","П\ятница","Суббота"];

   var months = ["\январ\я","феврал\я","марта","апрел\я",
               "ма\я","июн\я","июл\я","августа","сентябр\я",
               "октябр\я","ноябр\я","декабр\я"];

   var year = this.getYear();
   if (year < 1900) year += 1900;

   return days[this.getDay()]+", "+this.getDate()+" "+
      months[this.getMonth()]+" "+year+"г.";
}

Опишем две даты, a и b и привяжем метод russian к экземпляру b:

var a = new Date(98,11,31);
var b = new Date();
b.russian = _dateRussian;
document.write(b.russian());

Все получается, смотрите!

Однако, если мы попытаемся воспользоваться методом russian() в контексте экземпояра a, нас ждет разочарование :-( Дело в том, что мы привязали новый метод не к объекту Date, но только к конкретному экземпляру b!

А можно ли ... - да можно! Как уже было сказано, еще и не то можно!

У каждого объекта в JavaScript есть специальное системное свойство prototype. По сути дела, это указатель на общую для всех экземпляров таблицу методов. Эти методы являются именно общими для всех экземпляров данного объекта. Кроме методов, перечисленных в prototype, у каждого эеземпляра есть еще и своя, локальная таблица методов.

Когда мы добавляли метод для конкретного экземпляра, мы добавляли его именно в локальную таблицу, поэтому, это не повлияло на другие экземпляры. Если же мы добавим метод в таблицу на которую указывает свойство prototype, то мы добавим этот метод одновременно для всех экземпляров.

У нас уже описаны два экземпляра Date. Добавим теперь метод russian() ко всему объекту и напечатаем оба экземпляра. Обратите внимание на синтаксис использования prototype.

Date.prototype.russian = _dateRussian;
document.write(a.russian()+"<BR>");
document.write(b.russian());

Получаем:

Таким образом, мы добавили новый метод к ранее существовашему (даже не нами определенному!) объекту.

Обратите внимание, это важно, все, что мы добавляем через prototype, мы добавляем ко всему объекту, т.е. ко всем его экземплярам, как уже созданным, так и к тем, что будут созданы в будующем! Когда мы добавляем свойство, оно добавляется ко всем экземплярам, однако, значение этого свойства будет уникальным для каждого экземпляра.

Рассмотрим пример. Мы уже создали два экземпляра объекта person. Попробуем теперь добавить к этому объекту новое свойство и пронаблюдаем, что получится.

// добавили свойство
Person.prototype.age = 25; 

// печатаем и видим, что age=25
// для обоих экземпляров!
document.write("До изменени\я: "+
   ivanov.age+" "+petrov.age+"<BR>");

// изменяем petrov.age 
petrov.age = 30;

// печатаем и видим, наше изменение  
// никак не повлияло на ivanov.age
document.write("После изменени\я: "+
   ivanov.age+" "+petrov.age+"<BR>");

Вот, что будет напечатано:

Ну, и, наконец, а можно ли изменить метод объекта, который в нем (в объекте) уже определен? Судя по описанию языка, можно. В реальной же жизни, ответ, зависит от Вашего браузера. В MSIE - пожалуйста, в Netscape - нет (пока?)

У нас уже были определены два экземпляра объекта Date. Попробуем заменить родной Date'вский toString() на наш _dateRussian().

Date.prototype.toString = _dateRussian;
document.write(a);

Если Вы используете MSIE, дата будет напечатана по русски, если Netscape - нет.

В реальных программах, когда нужно добавить метод к существующему объекту, обычно используют оба подхода - и через prototype и добавление к конкретному экземпляру. Через prototype задается общий для всех метод (например, печать даты по русски), а экземпляру придается уникальный метод (например, печать праздничной даты по русски).

Создание объектов на базе существующих.
Наследование и полиморфизм

Объекты в програмировании используются для представления некоторых жизненных реалий. Но, в реальной жизни объекты не существуют сами по себе, а образуют довольно сложные структуры со сложными взаимосвязями. Один из основных видов взаимосвязей - наследование свойств.

Например, кошка, собака, мышь, кит, человек и т.д. безусловно являются различными существами. Однако, все они - млекопитающие и,значит, обладают целым рядом общих свойств. Хотя, разумеется, у них есть и уникальные свойства, которые и отличают их друг от друга.

В предыдущем разделе мы построили объект Man, который хранит информацию об имени и годе рождения человека и может вычислять возраст, пол и выдавать полное или сокращенное имя.

Если теперь мы захотим определить объект Student, который будет хранить информацию о номере группы и вычислять год поступления, специальность и т.д., то нам очень не хотелось бы заново переопределять все свойства и методы объекта Man. Гораздо приятнее было бы сказать примерно так: "студент, это человек, и ничто человеческое (включая имя, пол, возраст) ему не чуждо. Но, о студенте нам хотелось бы еще знать номер его группы, год поступления и специальность".

К счастью, такая возможность есть. Она реализуется за счет механизма, известного под названием "наследование свойств объекта". Не будет преувеличением сказать, что этот механизм - есть самая главная составляющая объектной модели.

Таким образом, мы можем строить сложную иерархию объектов. Сначала мы определяем самый общий объект, например "человек". Затем, мы определяем объекты, которые наследуют свойства и методы первого, но при этом добавляют свои (например, "студент", "военнослужащий", "ребенок" и т.д.) Далее мы можем создавать новые объекты, которые, уже наследуют свойства этих. Например, от объекта "ребенок" мы можем произвести объекты "грудной ребенок", "дошкольник", "школьник", "подросток". Объекты, которые наследуют свойства некоторого объекта называются его потомками, о тот - их родителем.

Очень важным здесь является тот факт, что тип объекта наследуется. Т.е. любой экземпляр объекта "школьник" будет одновременно являться и экземпляром объекта "ребенок" (а почему нет? У него ведь есть все необходимые свойства и методы), более того, этот же экземпляр, будет заодно являться и экземпляром объекта "человек".

Таким образом, если мы хотим применять метод getAge() к объекту "школьник", нам вовсе не нужно определять этот метод в самом объекте "школьник" (хотя и можно), достаточно, что он определен в объекте "человек"! Ведь любой экземпляр объекта "школьник" является вполне полноправным экземпляром объекта "человек".

Такая техника сохранения объектами-потомками типа объекта-родителя называется полиморфизмом (polymorphism). Говорят еще, что все объекты-потомки полиморфны относительно методов объекта-родителя.

Полиморфизм чрезвычайно важное понятие в информатике. Его роль выходит далеко за рамки отдельного языка и даже группы т.н. объектно-ориентированных языков. Чем лучше Вы его поймете и прочувствуете сейчас, тем понятнее Вам будут многие и многие разделы информатики и языки программирования.

Различают, по меньшей мере, два вида полиморфизма: статический и динамический. Иногда говорят о статическом и динамическом наследовании. В большинстве языков программирования реализован только статический полиморфизм. Это связано с тем, что динамический полиморфизм - довольно сложная и тонкая техника, которую трудно реализовать эффективно. К счастью, JavaScript позволяет нам познакомиться с обоими видами полиморфизма.

Повторяю, все это вовсе не означает, что мы не имеем права переопределить метод объекта-родителя в объекте потомке. Имеем! Просто мы имеем право не переопределять его, если он нам подходит. Напрмер, чуть позже мы произведем несколько объектов от объекта "человек". Для всех объектов-потомков, кроме "грудной ребенок" мы оставим метод getAge() унаследованный от объекта "человек", а для объекта "грудной ребенок" мы переопределим его так, чтобы он выдавал возраст не в годах, а в месяцах.

Статическое наследование

Итак попрбуем описать объект "ребенок" - потомок объекта человек. Пусть наш новый объект имеет все свойства и методы объекта-родителя и, в дополнение, свойства father и mother.

function Child(nm,by,f,m) {
   this.parent_object = Man;
   this.parent_object(nm,by);
   this.father = f;
   this.mother = m;
}

У конструктора этого объекта четыре параметра - имя и дата рождения (как и у объекта "человек"), и еще два - отец и мать (эти параметры должны быть уже готовыми экземплярами объекта "человек").

Обратите внимание, на первые две строки в конструкторе - это вызов конструктора объекта "человек". Именно за счет этого и осуществляется наследование. Сначала, мы делаем конструктор объекта Man методом объекта Child. Затем, просто, вызываем этот метод. Что он сделает? Конечно же, он добавит, к создаваемому нами, экземпляру объекта Child все свойства и методы объекта Man, т.е. сделает наш экземпляр объекта Child полноправным экземпляром объекта Man! Затем мы добавляем в него два новых свойства father и mother, которых у объекта Man не было.

Если мы немного подумаем, то поймем, что нас устраивает метод getAge() из объекта Man и менять его нам не нужно. А, вот, метод toString(), лучше бы поменять, чтобы информация о ребенке печаталась вместе с информацией о родителях. Перепишем конструтор и добавим функцию, реализующую метод.

function Child(nm,bd,f,m) {
   this.parent_object = Man;
   this.parent_object(nm,bd);
   this.father = f;
   this.mother = m;
   this.toString = _childToString;
}

function _childToString() {
   with (this) { return "<DL><DT><B>"+
         nameArray[0]+" "+nameArray[1]+
         " пол:"+((isMan()) ? "М":"Ж")+
         " возраст:"+getAge()+"</B></DT>"+
         "<DD>отец: "+father+
         "<BR>мать: "+mother+
         "</DD></DL>";
   }     
}

Попробуем создать несколько экземпляров объекта Child и посмотрим какая техника при этом используется:



// Сначала создадим родителей отдельно
var papa = new Man("Иванов Иван Иванович",new Date(1973,1,1));
var mama = new Man("Иванова Мари\я Федоровна",new Date(1976,8,11));

// Теперь создадим их ребенка
var vasya = new Child("Иванов Василий Иванович",new Date(1996,5,21),papa,mama);

// Попробуем создать всех одновременно
// для этого просто вызываем new Man()
// в качестве фактического параметра
Сначала содадим родителей отдельно
var lena = new Child("Петрова Елена Петровна",new Date(1995,4,12),
             new Man("Петров Петр Иванович",new Date(1972,2,21)),
             new Man("Петрова Наталь\я Николаевна",new Date(1974,9,12)));

document.write(vasya,lena);             

Напечатано будет:

Итак, что же мы сделали? Мы определили новый объект "ребенок". В этом объекте мы не определяли никаких методов, кроме toString(). Все остальные методы мы унаследовали от объекта "человек". Мы определили два новых свойства. Остальные свойства, такие как nameArray и birthDay мы унаследовали от того же объекта "человек".

Все это стало возможным благодаря технике полиморфизма. Мы просто объявили, что "ребенок" - есть человек, обладающий еще двумя свойствами и печатать информацию о нем нужно немного по другому.

Можно ли вызвать, например метод getName() в контексте объекта Child? Ведь в объекте Child мы не определяли такого метода! Разумеется можно! Мы ведь сказали, что Child - это прежде всего Man, значит все, что было определено для Man, автоматически определено и для Child. В этом смысл полиморфизма.

Определим еще один объект - "грудной ребенок". Этот объект будет полностью аналогичен объекту ребенок, за исключением того, что метод getAge() будет выдавать возраст не в годах, а в месяцах. Все остальное, наш новый объект должен унаследовать от объекта Child.

function Baby(nm,by,f,m) {
   this.parent_object = Child;
   this.parent_object(nm,by,f,m);
   this.getAge = _babyGetAge;
}

function _babyGetAge() {
   var today = new Date();

   // если ребенок родился в прошлом году,
   // нужно это учесть
   var year = today.getYear();
   var byear = this.birthDay.getYear();
   if (year < 1900) year += 1900;
   if (byear < 1900) byear += 1900;
   var months = (year-byear)*12;

   // вычислим полные месяцы
   months += (today.getMonth()-this.birthDay.getMonth());

   // если сегодняшнее число меньше числа
   // дня рождения, то мы прихватили
   // лишний месяц. Скорректируем 
   if (today.getDate() < this.birthDay.getDate()) months--;

   return months+" мес."; 
}

var tanya = new Baby("Петрова Татьяна Петровна",new Date(1998,0,12),
             new Man("Петров Петр Иванович",new Date(1972,2,21)),
             new Man("Петрова Наталь\я Николаевна",new Date(1974,9,12)));
var sasha = new Baby("Иванов Александр Петрович",new Date(1998,7,12),
             new Man("Иванов Петр Иванович",new Date(1972,2,21)),
             new Man("Иванова Наталь\я Николаевна",new Date(1974,9,12)));

document.write(tanya,sasha);

Вот, что будет напечатано:

Как видите, опять, мы только поменяли один метод. Более того, мы даже не вызываем этот измененный метод явно! Его вызывает toString(), который мы и не думали менять! Тем не менее, все работает, как надо.

Огромная просьба к читателю. Убедитесь, что этот материал понят. Если это не так, перечитайте и разберите примеры еще раз. Если опять непонятно, обратитесь к преподавателю. Без понимания этого материала Вам нет никакого смысла читать дальше!

Динамическое наследование

До сих пор, мы имели дело с т.н. статическим наследованием, когда экземпляр объекта - потомка, скопировав у объекта - родителя его таблицу свойств и методов, напрочь забывал о существовании родителя. Никакие изменения в объекте - родителе, произошедшие после создания экземпляра объекта - потомка, никак не влияли на этот экземпляр.

Поясним это на примере. Создадим объект Increaser, который хранит число и умеет увеличивать его на единицу.

function Increaser (n) {
   this.value = n;
   this.increase = _increaserInc1;
   this.toString = _increaserToString;
}

function _increaserInc1 () {
   this.value++;
}

function _increaserToString () {
   return "Value="+this.value;
}

Теперь, создадим объект - потомок StaticIncreaser. Пока он будет ровно ткаим же как и его предок и все унаследует от того.

function StaticIncreaser (n) {
   this.parent_object = Increaser;
   this.parent_object(n);
}

Проверим, как все работает:

var a = new StaticIncreaser(2);
a.increase();
document.write("A: "+a);

Напечатано будет:

Допустим, что теперь, после того, как мы уже создали экземпляр a, мы решили добавить к объекту Increaser новый метод increase10, увеличивающий число не на 1, а на 10. Сделаем это.

function _increaserInc10 () {
   this.value += 10;
}
Increaser.prototype.increase10 = _increaserInc10;

Если мы попытаемся сейчас, обратиться к методу increase10, в контексте ранее созданного экземпляра a, это вызовет ошибку.

Дело в том, что a, экземпляр объекта StaticIncreaser. Наследование было статическим. Это значит, что немедленно после своего создания, экземпляр a потерял всякую связь с родительским объектом Increaser и никакие изменения последнего не касаются экземпляра a.

Если такая связь между объектом-родителем и экземплярами объектов потомков очень желательно, то альтернативой здесь может служить динамическое наследование. Синтаксически, оно оформляется через свойство объекта prototype.

function DynamicIncreaser (n) {
   this.value = n;
}
DynamicIncreaser.prototype = new Increaser;

Запомните этот синтаксис, он всегда одинаковый. Означает это, что вновь созданный объект DynamicIncreaser будет использовать таблицу методов объекта Increaser. Таким образом все изменения в Increaser будут мемедленно отражаться на экземплярах объекта DynamicIncreaser.

Попробуем теперь сначала создать экземпляр b, затем добавить к объекту Increaser новый метод increase5 и посмотреть, как наш экземпляр b, сразу же, сможет им пользоваться.

function _increaserInc5 () {
   this.value += 5;
}
Increaser.prototype.increase5 = _increaserInc5;

var b = new DynamicIncreaser(3);
b.increase();
document.write("B: "+b);
Increaser.prototype.increase5 = _increaserInc5;
b.increase5();
document.write("<BR>B: "+b);

Будет напечатано:

Следующий (и последний, на сегодня) пример будет работать только в Netscape Navigator. Не знаю, почему он не работает в MSIE. Тем не менее, не могу удержаться и не показать пример наследования от системмного объекта.

Пусть нам нужен объект - "группа людей". У группы есть название. Туда можно добавлять имена. Группу можно печатать. Наклнец, при печати, фамилии должны печататься в алфавитном порядке. Попробуем определить такой объект.

Для начала, заметим, что нам вполне подойдет Array, в качестве объекта - родителя. Там мы сможем пользоваться готовой сортировкой. Все, что нам нужно, это добавить свои функции add и toString.

function Group(nm) {
   this.toString = _groupToString;
   this.add = _groupAdd;
   this.name = nm;
}
Group.prototype = new Array;

function _groupAdd (item) {
   this[this.length] = item;
}

function _groupToString(nm) {
   this.sort();
   var result = "<B>"+this.name+"</B><UL>";
   for (var i=0; i<this.length; i++) {
      result += ("<LI>"+this[i]+"</LI>");
   }
   return result+"</UL>";
}

var group = new Group("Участники семинара");
group.add("Иванов И.И.");
group.add("Петров П.П.");
group.add("Аносов А.В.");
group.add("Ямщиков В.П.");
group.add("Бакунин Л.Д.");

document.write(group);

Вот, что будет напечатано:

Вот и все, на сегодня. На самом деле мы рассмотрели полиморфизм далеко не полностью. Это понятие может стать темой десятка лекций. Однако, я надеюсь, что Вы получили достаточно материала для того, чтобы иметь возможность начать писать программы с использованием этой возможности. В дальнейшем, вместе с опытом и чтением другой литературы, Вы узнаете о нем больше.


До сессии всего 35 дней!!!

Главная страница Замечания? Комментарии? Идеи?